package cn.itcast.nems.studentrecord.service.impl;

import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.order.dto.OrderMode;
import cn.itcast.nems.order.dto.OrderModeLineDTO;
import cn.itcast.nems.order.dto.PaymentFlowDTO;
import cn.itcast.nems.studentrecord.constant.OrderSourceEnum;
import cn.itcast.nems.studentrecord.constant.ReferenceTypeEnum;
import cn.itcast.nems.studentrecord.converter.PaymentFlowConverter;
import cn.itcast.nems.studentrecord.converter.StudentRecordFundFlowConverter;
import cn.itcast.nems.studentrecord.converter.StudentRecordReceivableConverter;
import cn.itcast.nems.studentrecord.converter.StudentRecordReferenceConverter;
import cn.itcast.nems.studentrecord.dao.entity.StudentRecord;
import cn.itcast.nems.studentrecord.dao.entity.StudentRecordTransaction;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeCompleteResultDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeResultDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordJournalDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordTransferDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordFundFlowDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordOrderCreateResultDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordReceivableCreateDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordReferenceDTO;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordErrorBodyEnum;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordStateEnum;
import cn.itcast.nems.studentrecord.service.settlement.*;
import cn.itcast.nems.studentrecord.service.studentrecord.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.List;

import static cn.itcast.nems.journal.util.TransferUtils.localToTime;

/**
 * @author liyong
 * create: 2023-11-21 15:56:22
 **/
@Service
public class StudentRecordTransferServiceImpl extends StudentRecordChangeServiceImpl implements StudentRecordTransferService {
    private final OrderApiDelegate orderApiDelegate;
    private final StudentRecordFundFlowService studentRecordFundFlowService;

    StudentRecordTransferServiceImpl(StudentRecordTransactionService studentRecordTransactionService,
                                     OrderApiDelegate orderApiDelegate,
                                     StudentRecordService studentRecordService,
                                     StudentRecordCreateService studentRecordCreateService,
                                     StudentRecordMessageService studentRecordMessageService,
                                     StudentRecordFundFlowService studentRecordFundFlowService,
                                     StudentRecordReceivableService studentRecordReceivableService) {
        super(studentRecordService,
                studentRecordCreateService,
                studentRecordTransactionService,
                studentRecordMessageService,
                studentRecordReceivableService,
                orderApiDelegate.getOrderApi());
        this.orderApiDelegate = orderApiDelegate;
        this.studentRecordFundFlowService = studentRecordFundFlowService;
    }

    @Override
    @Transactional
    public StudentRecordChangeResultDTO transfer(StudentRecordTransferDTO studentRecordTransfer) {
        final String type = studentRecordTransfer.getJournalType().getName();
        logger.info("【{}】创建入班学籍信息开始{}", type, studentRecordTransfer);
        super.checkChange(studentRecordTransfer, checker -> checker.setIgnoredLock(true));
        StudentRecordOrderCreateResultDTO orderResult = this.createOrderIfNeed(studentRecordTransfer);
        String orderId = orderResult == null ? "" : orderResult.getOrderResultDTO().getOrderId();
        try {
            studentRecordTransfer.setTargetClazzState(StudentRecordStateEnum.VALID);
            logger.info("出班学籍'{}' 对应入班创建的订单ID: '{}'", studentRecordTransfer.getOriginStudentRecordId(), orderId);
            StudentRecord studentRecord = super.createTargetStudentRecord(studentRecordTransfer, orderId, false);
            logger.info("【{}】创建入班学籍{}结束{}", type, studentRecord.getId(), studentRecordTransfer);
            return new StudentRecordChangeResultDTO(
                    false,
                    null,
                    studentRecord.getId(),
                    orderId
            );
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
            if (StringUtils.hasText(orderId)) {
                this.orderApiDelegate.cancel(orderId);
            }
            throw e;
        }
    }

    @Override
    @Transactional
    public StudentRecordChangeCompleteResultDTO transferComplete(StudentRecordJournalDTO studentRecordJournal) {
        final String type = studentRecordJournal.getJournalType().getName();
        logger.info("【{}】完成开始{}", type, studentRecordJournal);
        StudentRecord originSr = this.studentRecordService.getById(studentRecordJournal.getOriginStudentRecordId());
        if(originSr.getSettlementDate() != null){
            logger.info("凭单'{}'已经结算过忽略请求", studentRecordJournal.getJournalId());
            return new StudentRecordChangeCompleteResultDTO(studentRecordJournal.getTargetStudentRecordId(), null);
        }
        StudentRecord targetSr = this.studentRecordService.getById(studentRecordJournal.getTargetStudentRecordId());
        final SettlementResult settlementResult = this.processOriginalClazz(studentRecordJournal, originSr, targetSr.getActualAmountDue());
        targetSr.setTransferFromAmount(settlementResult.getTransferAmount());
        PaymentFlowDTO flowDTO = this.processTargetClazz(studentRecordJournal, targetSr);
        logger.info("【{}】完成结束{}", type, studentRecordJournal);
        return new StudentRecordChangeCompleteResultDTO(studentRecordJournal.getTargetStudentRecordId(), flowDTO);
    }

    @Override
    public void addOrderFlow(List<PaymentFlowDTO> flows) {
        this.studentRecordMessageService.sendOrderFlowMessages(flows);
    }

    private SettlementResult processOriginalClazz(StudentRecordJournalDTO studentRecordJournal,
                                                  StudentRecord sr,
                                                  int targetActualAmountDue) {
        // 处理转出班
        final SettlementResult settlementResult = this.updateOriginClazzSettlement(studentRecordJournal);
        logger.info("出班学籍'{}'结算信息{}", studentRecordJournal.getOriginStudentRecordId(), settlementResult);
        StudentRecordReferenceDTO dto = StudentRecordReferenceConverter.origin(sr, studentRecordJournal);
        logger.info("出班学籍创建流转记录{}", dto);
        super.createStudentRecordTransaction(dto, settlementResult, StudentRecordTransaction.TYPE_OUT);
        int transferAmount = settlementResult.getTransferAmount();
        logger.info("出班学籍转出金额'{}'", transferAmount);
        if (transferAmount > 0) {
            logger.info("出班学籍创建学籍流水{} 金额{}", sr.getId(), -transferAmount);
            this.addStudentRecordFundFlow(
                    sr, -transferAmount, studentRecordJournal, false,
                    localToTime(studentRecordJournal.getBizDate()),
                    studentRecordJournal.getPaymentAccountCode(),
                    studentRecordJournal.getPaymentAccountName(),
                    studentRecordJournal.getMultiPaymentAccountFlag(),
                    studentRecordJournal.getOriginPaymentAccountCode(),
                    studentRecordJournal.getOriginPaymentAccountName()
            );
        }
        final StudentRecordReceivableCreateDTO recordReceivableDTO = StudentRecordReceivableConverter.createDTO(studentRecordJournal,
                studentRecordJournal.getOriginStudentRecordId(),
                -targetActualAmountDue);
        logger.info("出班学籍创建应收信息{}", recordReceivableDTO);
        super.addStudentRecordReceivable(recordReceivableDTO);
        super.studentRecordService.unlock(studentRecordJournal.getOriginStudentRecordId());
        return settlementResult;
    }

    private PaymentFlowDTO processTargetClazz(StudentRecordJournalDTO studentRecordJournal, StudentRecord sr) {
        logger.info("更新入班学籍'{}'状态", sr.getId());
        super.studentRecordChangeComplete(sr, studentRecordJournal.getTargetClazzInDate());
        super.popOrderIfExsit(sr.getId(), studentRecordJournal);
        StudentRecordReferenceDTO dto = StudentRecordReferenceConverter.target(sr, studentRecordJournal);
        logger.info("创建入班学籍流转记录'{}'", dto);
        super.createStudentRecordTransaction(dto, null, StudentRecordTransaction.TYPE_IN);
        this.processTargetStudentRecordReceivable(studentRecordJournal, sr);
        int transferAmount = sr.getTransferFromAmount();
        logger.info("入班学籍'{}',出班学籍转入金额'{}',类型{}", sr.getId(), transferAmount, studentRecordJournal.getJournalType());
        if (transferAmount > 0) {
            if (studentRecordJournal.hasOrder()) {
                final boolean b = this.studentRecordFundFlowService.exsitReference(sr.getId(), studentRecordJournal.getOrderId(), studentRecordJournal.getOrderLineId());
                if (b) {
                    logger.info("支付流水已经存在 {}", studentRecordJournal);
                } else {
                    // 如果转入班有订单，则在订单上添加流水，学籍流水等到收到订单支付消息时添加
                    logger.info("异步给订单添加流水 {}", studentRecordJournal);
                    // LocalDateTime bizDate = studentRecordJournal.getTargetClazzInDate().atStartOfDay();
                    LocalDateTime bizDate = LocalDateTime.now();
                    return PaymentFlowConverter.createPaymentFlowDTO(studentRecordJournal.getOrderId(),
                            transferAmount,
                            studentRecordJournal.getJournalLineId(),
                            bizDate);
                }
            } else {
                logger.info("转入班学籍添加流水'{}' '{}'", sr.getId(), transferAmount);
                this.addStudentRecordFundFlow(
                        sr, transferAmount, studentRecordJournal, true,
                        localToTime(studentRecordJournal.getBizDate()),
                        studentRecordJournal.getPaymentAccountCode(),
                        studentRecordJournal.getPaymentAccountName(),
                        studentRecordJournal.getMultiPaymentAccountFlag(),
                        studentRecordJournal.getOriginPaymentAccountCode(),
                        studentRecordJournal.getOriginPaymentAccountName()
                );
            }
        }
        return null;
    }

    private void processTargetStudentRecordReceivable(StudentRecordJournalDTO studentRecordJournal,
                                                      StudentRecord sr) {
        if (studentRecordJournal.hasOrder()) {
            final String orderId = this.studentRecordService.findOrderId(studentRecordJournal.getTargetStudentRecordId());
            final OrderMode orderMode = this.orderApiDelegate.findById(orderId);
            final StudentRecordReceivableCreateDTO dto = StudentRecordReceivableConverter.createDTO(orderMode, studentRecordJournal.getTargetStudentRecordId());
            final List<OrderModeLineDTO> lines = this.orderApiDelegate.getOrderApi().findOrderLines(orderId);
            final String lineId = lines.get(0).getId();
            dto.setReferenceLineId(lineId);
            logger.info("创建转入班学籍(转移订单)应收'{}'", dto);
            super.addStudentRecordReceivable(dto);
        } else {
            final StudentRecordReceivableCreateDTO dto = StudentRecordReceivableConverter.createDTO(studentRecordJournal, sr.getId(), sr.getAmountDue());
            logger.info("创建{}学籍应收'{}'", studentRecordJournal.getJournalType().getName(), dto);
            super.addStudentRecordReceivable(dto);
        }
    }

    private SettlementResult updateOriginClazzSettlement(StudentRecordJournalDTO studentRecordJournal) {
        SettlementResult origin = studentRecordJournal.createSettlementResult();
        return super.studentRecordService.studentRecordSettlement(studentRecordJournal.getOriginStudentRecordId(),
                studentRecordJournal.getOriginClazzOutDate(),
                this.getCalculator(studentRecordJournal.getJournalType(), origin));
    }

    private void addStudentRecordFundFlow(
            StudentRecord studentRecord, int amount, StudentRecordJournalDTO studentRecordJournal,
            boolean updateStudentRecordPaid, LocalDateTime bizDate, String paymentAccountCode, String setPaymentAccountName,
            Integer multiPaymentAccountFlag, String originPaymentAccountCode, String originPaymentAccountName
    ) {
        StudentRecordFundFlowDTO dto = StudentRecordFundFlowConverter.createRefundTransferStudentRecordFundFlow(
                studentRecord, amount, studentRecordJournal,
                bizDate, paymentAccountCode, setPaymentAccountName,
                multiPaymentAccountFlag, originPaymentAccountCode, originPaymentAccountName
        );
        dto.setUpdateStudentRecord(updateStudentRecordPaid);
        this.studentRecordFundFlowService.create(dto);
    }

    private StudentRecordOrderCreateResultDTO createOrderIfNeed(StudentRecordTransferDTO studentRecordTransfer) {
        if (studentRecordTransfer.hasOrder()) {
            String bizOrderNo = studentRecordTransfer.getJournalLineId();
            OrderSourceEnum source = OrderSourceEnum.valueOf(studentRecordTransfer.getJournalType().name());
            return this.orderApiDelegate.createOrder(studentRecordTransfer, source, bizOrderNo);
        }
        return null;
    }

    /**
     * 根据凭单类型获取结算出班计算类
     *
     * @param type   凭单类型
     * @param origin 原始结算数据
     * @return 出班结算计算类
     * @author tristan_lau
     * @since 2023/12/12 19:36
     */
    private DefaultSettlementCalculator getCalculator(ReferenceTypeEnum type, final SettlementResult origin) {
        DefaultSettlementCalculator calculator = null;
        if (ReferenceTypeEnum.TRANSFER.equals(type)) {
            calculator = new SettlementCalculatorTransfer(origin);
        } else if (ReferenceTypeEnum.EXCHANGE.equals(type)) {
            calculator = new SettlementCalculatorExchange(origin);
        } else if (ReferenceTypeEnum.SUSPENSION.equals(type)) {
            calculator = new SettlementCalculatorSuspension(origin);
        } else if (ReferenceTypeEnum.DELAY.equals(type)) {
            calculator = new SettlementCalculatorDelay(origin);
        } else {
            BizExceptionProducer.throwProduce(StudentRecordErrorBodyEnum.TRANSFER_REFERENCE_TYPE_INCORRECT, type.name());
        }
        return calculator;
    }
}
